# _*_coding:utf-8_*_
'''
Created on Mar 13, 2014

@author: JZHAI
'''
import ctypes
import time
import sys

## for dio control, from DAQmx
DAQmx_Val_ChanPerLine = 0
DAQmx_Val_ChanForAllLines = 1

DAQmx_Val_Rising = 10280
DAQmx_Val_Falling = 10171

DAQmx_Val_CountUp = 10128
DAQmx_Val_CountDown = 10124
DAQmx_Val_ExtControlled = 10326

DAQmx_Val_GroupByChannel = 0
DAQmx_Val_GroupByScanNumber = 1


def posterror(msg):
    # return
    print("@@@@@@@Error", msg)


# load dll
dio_dll = ctypes.windll.LoadLibrary("nicaiu.dll")


# class DIO_NI(instrumentDriver.InstGen.DIO):
class DIO_NI:
    def __init__(self, name, info_dict):
        # instrumentDriver.InstGen.MTRX.__init__(self, name, info_dict)
        self.info_dict = info_dict
        self.name = name
        self.model = info_dict["MODEL"]
        # since only one task can run at once, here handler will be updated when \
        # each time CreateTask() called.
        self.handler = None
        self.dioinfo = self.getdiospec()

    def __gethandler(self):
        return self.handler

    def getdiospec(self):
        dioinfo = {}
        if self.model == "6501":
            dioinfo["port_cnt"] = 3
            dioinfo["port_width"] = 8
        return dioinfo

    def getlineconfig(self):
        lineconfig = {}
        use_line = []
        for k, v in self.info_dict:
            if k.startswith("LINE"):
                l_ind = int(k[4:])
                lineconfig[l_ind] = v
                use_line.append(l_ind)
        use_line.sort()
        lineconfig["use_line"] = use_line
        self.lineconfig = lineconfig
        return lineconfig

    def GetErrorString(self, err_code):
        """
        int32 DAQmxGetErrorString (int32 errorCode, char errorString[], uInt32 bufferSize);
        """
        global dio_dll
        errorCode = ctypes.c_int32(err_code)
        errorString = ctypes.create_string_buffer("", 1000)
        bufferSize = ctypes.c_uint32(1000)
        stat = dio_dll.DAQmxGetErrorString(errorCode, ctypes.byref(errorString), bufferSize)
        # print("GetErrorString stat", stat)
        return repr(errorString.value)

    def CreateTask(self, task_name=""):
        """
        int32 DAQmxCreateTask (const char taskName[], TaskHandle *taskHandle);
        """
        global dio_dll
        task_name = ctypes.c_char_p(task_name)
        task_handle = ctypes.c_uint32(0)
        stat = dio_dll.DAQmxCreateTask(task_name, ctypes.byref(task_handle))
        if stat != 0:
            err_str = self.GetErrorString(stat)
            posterror("Error in InstGenDIO_NI.CreateTask() function. " + err_str)
            return stat
        self.handler = task_handle
        return task_handle

    def CreateDOChan(self, chan_name):
        """
        chan_name(DIO channel name):["port0/line0:2","port1/line1","port0:1","line0:31"]
        int32 DAQmxCreateDOChan (TaskHandle taskHandle, const char lines[], const char nameToAssignToLines[], int32 lineGrouping);
        """
        global dio_dll
        dev_id = self.info_dict.get("DEV_NUM", 1)
        chan_name = ["dev%d/" % dev_id + s for s in chan_name]
        chan_name = ",".join(chan_name)
        lines = ctypes.c_char_p(chan_name)
        nameToAssignToLines = ctypes.c_char_p("")
        lineGrouping = ctypes.c_ushort(DAQmx_Val_ChanForAllLines)
        print("*****", lines)
        stat = dio_dll.DAQmxCreateDOChan(self.handler, lines, nameToAssignToLines, lineGrouping)
        if stat != 0:
            err_str = self.GetErrorString(stat)
            posterror("Error in InstGenDIO_NI.CreateDOChan() function. " + err_str)
            return stat
        return stat

    def CreateCICountEdgesChan(self, chan_name="ctr0"):
        """
        edge = DAQmx_Val_Falling  P2.7 can be configured as a counter, which only can \
            monitor the signal from high to low, it is falling edge.
        countDirection = DAQmx_Val_CountUp
        int32 DAQmxCreateCICountEdgesChan (TaskHandle taskHandle, const char counter[], const char nameToAssignToChannel[], int32 edge, uInt32 initialCount, int32 countDirection);
        """
        global dio_dll
        dev_id = self.info_dict.get("DEV_NUM", 1)
        counter = "dev%d/" % dev_id + chan_name  # only one counter channel supported for one task
        counter = ctypes.c_char_p(counter)
        nameToAssignToChannel = ctypes.c_char_p("")
        edge = ctypes.c_int32(DAQmx_Val_Falling)
        initialCount = ctypes.c_uint32(0)
        countDirection = ctypes.c_int32(DAQmx_Val_CountUp)
        stat = dio_dll.DAQmxCreateCICountEdgesChan(self.handler, counter, nameToAssignToChannel, edge, initialCount,
                                                   countDirection)
        if stat != 0:
            err_str = self.GetErrorString(stat)
            posterror("Error in InstGenDIO_NI.CreateCICountEdgesChan() function. " + err_str)
            return stat
        return stat

    def StartTask(self):
        """
        int32 DAQmxStartTask (TaskHandle taskHandle);
        """
        global dio_dll
        stat = dio_dll.DAQmxStartTask(self.handler)
        if stat != 0:
            err_str = self.GetErrorString(stat)
            posterror("Error in InstGenDIO_NI.StartTask() function. " + err_str)
            return stat
        return stat

    def StopTask(self):
        """
        int32 DAQmxStopTask (TaskHandle taskHandle);
        """
        global dio_dll
        stat = dio_dll.DAQmxStopTask(self.handler)
        if stat != 0:
            err_str = self.GetErrorString(stat)
            posterror("Error in InstGenDIO_NI.StopTask() function. " + err_str)
            return stat
        return stat

    def ClearTask(self):
        """
        int32 DAQmxClearTask (TaskHandle taskHandle);
        """
        global dio_dll
        stat = dio_dll.DAQmxClearTask(self.handler)
        if stat != 0:
            err_str = self.GetErrorString(stat)
            posterror("Error in InstGenDIO_NI.ClearTask() function. " + err_str)
            return stat
        return stat

    def ReadCounterScalarU32(self, timeout=0):
        """
        int32 DAQmxReadCounterScalarU32 (TaskHandle taskHandle, \
            float64 timeout, uInt32 *value, bool32 *reserved);
        """
        global dio_dll
        timeout = ctypes.c_double(timeout)
        reserved = None
        value = ctypes.c_uint32(0)
        stat = dio_dll.DAQmxReadCounterScalarU32(self.handler, timeout, \
                                                 ctypes.byref(value), reserved)
        if stat != 0:
            err_str = self.GetErrorString(stat)
            posterror("Error in InstGenDIO_NI.ReadCounterScalarU32() function. " + err_str)
            return stat
        return value.value

    def WriteDigitalLines(self, write_list):
        """
        When you create your write array, each sample per channel must contain \
        the number of bytes returned by the DAQmx_Write_DigitalLines_BytesPerChan\
         property.(Currently no check)
        int32 DAQmxWriteDigitalLines (TaskHandle taskHandle, \
            int32 numSampsPerChan, bool32 autoStart, float64 timeout, \
            bool32 dataLayout, uInt8 writeArray[], int32 *sampsPerChanWritten,\
             bool32 *reserved);
        """
        global dio_dll
        numSampsPerChan = ctypes.c_int32(1)
        autoStart = ctypes.c_ushort(0)
        timeout = ctypes.c_double(10)
        dataLayout = ctypes.c_ushort(DAQmx_Val_GroupByChannel)
        writeArray = (ctypes.c_uint8 * len(write_list))(*write_list)
        sampsPerChanWritten = ctypes.c_int32(0)
        reserved = None
        stat = dio_dll.DAQmxWriteDigitalLines(self.handler, numSampsPerChan, \
                                              autoStart, timeout, \
                                              dataLayout, writeArray, \
                                              ctypes.byref(sampsPerChanWritten), \
                                              reserved)
        if stat != 0:
            err_str = self.GetErrorString(stat)
            posterror("Error in InstGenDIO_NI.WriteDigitalLines() function. " + err_str)
            return stat
        return stat


info_dict = {"MODEL": "6501", "DEV_NUM": 1, "LINE17": "BIN0", "LINE16": "BIN1", "LINE26": "EOM"}
test_time_elapsed = 0.2
dio = DIO_NI("DIO1", info_dict)
# Bin3,Bin2,Bin1,Bin0,EOM
dio_out = ["port1/line4", "line15", "line16", "line17", "line26"]
# dio_out = ["line0:4"]
rst_level = [1] * len(dio_out)
bin_assign_val = [1, 1, 1, 0, 1]  # Always Bin0
eom_val = [1, 1, 1, 1, 0]  # line26 output low level signal
t0 = time.clock()
#  Automation begin, reset the dio out to high level
dio.CreateTask()
dio.CreateDOChan(dio_out)
sys.exit()
dio.StartTask()
dio.WriteDigitalLines(rst_level)  # reset to high level
dio.StopTask()
dio.ClearTask()
#  Automation begin, start counter monitor task
dio.CreateTask()
dio.CreateCICountEdgesChan()
dio.StartTask()
t1 = time.clock() - t0
print("^Automation begin, DIO ready.", t1)
# pop up msg dlg
answer = msgdlg("Please Start Handler.")
if not answer:
    print("Automation exits by user.")
    sys.exit()
t0 = time.clock()

device_cnt = 5  # Here indicate how many devices to be tested
print("^Start Device Test. Device count:", device_cnt)
for d in range(1, device_cnt + 1):
    # Device Begin. Read counter value, waiting till the value 1 got.
    val = 0
    while not val:
        val = dio.ReadCounterScalarU32()
        time.sleep(0.005)
    dio.StopTask()  # stop monitor
    dio.ClearTask()  # Clear then create dio out task when device end
    print("***Device Begin, got 'Start Test' signal from Handler.", time.clock() - t0)
    # Device Begin. After counter monitor, Reset DIO out to high level.
    if d > 1:  # Don't need to do reset for the first device, because the reset already done in at automation begin.
        dio.CreateTask()
        dio.CreateDOChan(dio_out)
        dio.StartTask()  # Can removed, setup autostart in WriteDigitalLines
        dio.WriteDigitalLines(rst_level)  # reset to high level
        dio.StopTask()
        dio.ClearTask()
        print("***Device Begin, Reset DIO out to High.", time.clock() - t0)
    time.sleep(test_time_elapsed)  # device tests cost 200ms.
    print("^^^^End Device %d tests... Here use 0.2sec delay instead." % d, time.clock() - t0)
    # Device End, output bin signal to hander, then send EOM(end of measure)
    dio.CreateTask()
    dio.CreateDOChan(dio_out)
    dio.StartTask()  # Write Bin
    ## This two lines write can be combined to once call then.
    dio.WriteDigitalLines(bin_assign_val)
    dio.WriteDigitalLines(eom_val)
    dio.StopTask()
    dio.ClearTask()
    print("%%%Device End, sent bin value then EOM signal.", time.clock() - t0)
    # Device End, restart counter monitor task.
    dio.CreateTask()
    dio.CreateCICountEdgesChan()
    dio.StartTask()  # start monitor
    print("%%%Device End, restarted counter monitor task.", time.clock() - t0)
    ####################################################################################
    """
    The time interval between sending EOM signal and starting counter monitor task
    is about 0.1sec. There is a risk that dio counter cannot capture the "start test"
    signal from handler if the time cost of "place the device by handler" is shorter
    than 0.1sec.
    """
    ###################################################################################
print("^%d devices test completed. Tatal test time %g. Substract test elapsed time, %g" \
      % (device_cnt, time.clock() - t0, time.clock() - t0 - device_cnt * test_time_elapsed))
